# [十] Spring AOP - ApectJ解析
导读
Spring AOP
与ApectJ
的目的一致,都是为了统一处理横切业务,但与AspectJ
不同的是,Spring AOP
并不尝试提供完整的AOP功能(即使它完全可以实现),Spring AOP
更注重的是与Spring IOC
容器的结合,并结合该优势来解决横切业务的问题,因此在AOP的功能完善方面,相对来说AspectJ
具有更大的优势,同时,Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP
的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别。
Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
# 开启Aspect静态代理
项目中如果某个类上使用了@Aspect
注解,来使用面向切面编程处理业务,那么前提一定要先通知Spring 开启支持aspectj
代理的配置,开启方式有两种:
- 基于XMl配置:
<aop:aspectj-autoproxy />
- 基于注解配置:
@EnableAspectJAutoProxy
# @EnableAspectJAutoProxy
测试类
@Service
/*
* TODO:
* 开启注解AOP
* */
@EnableAspectJAutoProxy(proxyTargetClass = false,exposeProxy = true)
public class EnableAspectJAutoProxyBean {
}
@EnableAspectJAutoProxy
解读
- 1、首先Srping容器初始化会通过
@ComponentScan
扫描到@Service
。 - 2、接着会扫描
@EnableAspectJAutoProxy
注解中的@Import
注解中的类。 - 3、扫描完后会分别把
EnableAspectJAutoProxy
和AspectJAutoProxyRegistrar
注册到BeanDefinition对象中。 - 4、继续循环
AspectJAutoProxyRegistrar
类中是否有@Component
、@ComponentScan
、@Import
等注解,如果有继续注册到BeanDefinition对象中。
@EnableAspectJAutoProxy
注解源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
/**
* true:
* 1、目标对象实现了接口 – 使用CGLIB代理机制
* 2、目标对象没有接口(只有实现类) – 使用CGLIB代理机制
*
* false:
* 1、目标对象实现了接口 – 使用JDK动态代理机制(代理所有实现了的接口)
* 2、目标对象没有接口(只有实现类) – 使用CGLIB代理机制
*
*/
boolean proxyTargetClass() default false;
/**
* 是否由Spring AOP 暴露代理(代理对象用ThreadLocal存起来)
*/
boolean exposeProxy() default false;
}
@Import(
AspectJAutoProxyRegistrar
.class) 源码
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//注解AOP入口类,注册一个叫"org.springframework.aop.config.internalAutoProxyCreator"
//的自动代理bean名字,覆盖AnnotationAwareAspectJAutoProxyCreator类,
//并注册到BeanDefinition中。
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
// 拿到EnableAspectJAutoProxy注解类中的属性值
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
// 如果有属性值
if (enableAspectJAutoProxy != null) {
// 如果proxyTargetClass属性值为true
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
// 强制使用代理类进行注册
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
//是否需要把代理对象暴露出来,简单来说是否需要把代理对象用ThreadLocal存起来,如果是true就是需要
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
// 强制使用代理类进行注册
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
问题
应用的业务场景:当一个类中A方法调用B方法执行事务操作时,B中不会有事务,因为a中调用b属于内部调用,没有通过代理,所以不会有事务产生。如何解决?
参考答案
首先设置expose-proxy()
属性为true
的时候,意味着将代理对象暴露出来,实际上调用了AopContext.setCurrentProxy(proxy)
方法保存当前线程关联的AOP代理对象 到ThreadLocal
中。再通过AopContext.currentProxy()
获取当前代理,然后通过代理调用B()方法。即 (当前类)AopContext.currentProxy()).B()
;
AopContext 源码
public final class AopContext {
/**
* 保存了当前线程关联的 AOP代理对象
*/
private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy");
private AopContext() {
}
/**
* TODO: 返回当前的代理对象
* 此方法使用的前提是通过AOP的方式激活,并且已经被AOP暴露
*/
public static Object currentProxy() throws IllegalStateException {
Object proxy = currentProxy.get();
if (proxy == null) {
throw new IllegalStateException(
"Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");
}
return proxy;
}
@Nullable
/**
* TODO: 保存代理对象到ThreadLocal
*/
static Object setCurrentProxy(@Nullable Object proxy) {
Object old = currentProxy.get();
if (proxy != null) {
currentProxy.set(proxy);
}
else {
currentProxy.remove();
}
return old;
}
}
扩展阅读
判断一个Bean是否是AOP代理对象可以使用如下三种方法:
- AopUtils.isAopProxy(bean) : 是否是代理对象;
- AopUtils.isCglibProxy(bean): 是否是CGLIB方式的代理对象;
- AopUtils.isJdkDynamicProxy(bean) : 是否是JDK动态代理方式的代理对象;
# 附:切面中的常用术语
1)连接点(Joinpoint) 程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些点中的特定点就称为
连接点
。Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位。2)切点(Pointcut) 每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。AOP通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。在Spring中,切点通过
org.springframework.aop.Pointcut
接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责切点所设定的查询条件,找到对应的连接点。其实确切地说,不能称之为查询连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息。3)增强(Advice) 增强是织入到目标类连接点上的一段程序代码,在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点。
4)目标对象(Target) 增强逻辑的织入目标类。如果没有AOP,目标业务类需要自己实现所有逻辑,而在AOP的帮助下,目标业务类只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用AOP动态织入到特定的连接点上。
5)引介(Introduction) 引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
6)织入(Weaving) 织入是将增强添加对目标类具体连接点上的过程。AOP像一台织布机,将目标类、增强或引介通过AOP这台织布机天衣无缝地编织到一起。根据不同的实现技术,AOP有三种织入的方式:
- a、编译期织入,这要求使用特殊的Java编译器。
- b、类装载期织入,这要求使用特殊的类装载器。
- c、动态代理织入,在运行期为目标类添加增强生成子类的方式。